昨天我們了解到 Migration 做的事情,也透過 Migration 建立了第一個 Table,還處理了 Model 和 Table 單數複數的轉換問題,今天將會開始來實作 ORM 的新增資料部分
還記得我們在之前的 file_model
也做過類似的事情,但不同以往的是,這次我們真的要用 SQL 語法來新增一筆資料,而不是用新增檔案的方式,在開始講解前,先直接貼上今天的程式碼吧
# mavericks/lib/mavericks/sqlite_model.rb
# .
# .
# (略)
module Mavericks
module Model
class SQLite
def initialize(data = nil)
@hash = data
end
def self.to_sql(val)
case val
when Numeric
val.to_s
when String
"'#{val}'"
else
raise "Can't support #{val.class} to SQL!"
end
end
def self.create(values)
values.delete :id
keys = schema.keys - ['id']
vals = keys.map do |key|
values[key.to_sym] ? to_sql(values[key.to_sym]) : "null"
end
DB.execute <<-SQL
INSERT INTO #{table} (#{keys.join ","})
VALUES (#{vals.join ","});
SQL
data = Hash[keys.zip values.values]
sql = "SELECT last_insert_rowid();"
data["id"] = DB.execute(sql)[0][0]
self.new data
end
def self.count
DB.execute(<<-SQL)[0][0]
SELECT COUNT(*) FROM #{table}
SQL
end
# .
# .
# (略)
end
end
end
其實程式碼並不難,做得事情也很單純,就讓我來一步一步講解吧
前面曾經說過,我們會以 類別名稱
對應到 表格名稱
,那 物件
自然就對應到表格的 每一筆資料
,利用這樣的設計,讓我們可以做到 物件關聯對映
,也就是所謂的 ORM
,所以這裡我們可以用 initialize
來替每一個物件做初始化,當作新增資料時的接口,我們簡單的將資料以 Hash 的方式存在 instance variable
(實體變數)裡面,方便讓我們對資料進行操作
def initialize(data = nil)
@hash = data
end
接著我們建立了一個 class method
叫 to_sql
,用來處理寫入資料庫所需要的語法轉換,例如在 SQL語法 裡面,我們會把字串用單引號包起來
# 一般的 SQL 語法大概長這樣,會發現字串都會用 '' 來包住
INSERT INTO Tasks (C_Id, title, content)
VALUES (3, '鐵人30', '每天發一篇文章');
所以我們必須比照當初 Schema 的欄位類型,來轉換相對應的語法
def self.to_sql(val)
case val
when Numeric
val.to_s
when String
"'#{val}'"
else
raise "Can't support #{val.class} to SQL!"
end
end
還記得 Rails 怎麼新增一筆資料嗎?
Task.create("title": "鐵人30", "content": "每天一篇文章")
我們將會傳一個 Hash 來新增資料 ,處理方式很簡單,就是把 key 和 value 做分離,這邊有個地方要注意的是,處理的過程中,要把 ID
拿掉,因為在 INSERT
的過程中, SQLite 會自動生成一個 ID
,處理的程式碼如下
values.delete :id
keys = schema.keys - ['id']
vals = keys.map do |key|
values[key.to_sym] ? to_sql(values[key.to_sym]) : "null"
end
會看到我們取得昨天做得 schema
並且把 ID
去掉,接著利用 map
來處理每個欄位資料
# vals 會長這樣
# vals = ["'鐵人30'", "'每天一篇文章'"]
接著利用 SQL 語法的INSERT INTO
INSERT INTO table_name (column1, column2, column3, ...)
VALUES (value1, value2, value3, ...);
加上之前處理好的 key 和 value,用 heredoc 串起 SQL 語法並且執行,這時候才是真正將資料存在資料庫裡面
DB.execute <<-SQL
INSERT INTO #{table} (#{keys.join ","})
VALUES (#{vals.join ","});
SQL
最後完成 create 以後,這邊我們用 zip
,來將 key 和 value 做 merge,然後重新 new 一個物件,記得要把 SQL 產生的 ID
值加回去,因為每新增一筆資料資料庫會自動生成 ID
,靠著內建 SQL 的語法 last_insert_rowid
我們會知道剛剛新增的 ID
是多少,並且加回去新 new 出來的物件,這樣之後對這個物件進行操作時,不管是刪除還是修改,才知道要依據那個 ID
的資料做操作
data = Hash[keys.zip values.values]
sql = "SELECT last_insert_rowid();"
data["id"] = DB.execute(sql)[0][0]
self.new data
另外為了讓我們方便知道資料有沒有成功新增,就一起把 count
的方法也加上去,這裡利用 SQL 語法的 COUNT
來得知現在總共有多少筆資料,因為 DB.execute
最後會回傳像 [[5]]
這樣的結構,所以我們要用 [0][0]
來取得 5
def self.count
DB.execute(<<-SQL)[0][0]
SELECT COUNT(*) FROM #{table}
SQL
end
這樣實作部分的程式碼都解說完了,讓我們回到 just_do 的 sqlite_test.rb
來執行看看結果吧
require 'sqlite3'
require 'mavericks/sqlite_model'
class Task < Mavericks::Model::SQLite
end
Task.create('title': '鐵人30', 'content': '每天發一篇')
puts Task.count
試試看每執行一次 sqlite_test.rb
,印出來的數字應該都會 +1
,如果有順利 +1
代表資料數量有增加,也代表我們成功新增資料了
$ bundle exec ruby sqlite_test.rb
# 1
看起來現在我們有初步的 ORM 功能,也把 CRUD
的 C
給完成了,明天會繼續將其他功能陸續補上,但如果對於其他部分已經知道怎麼做的人,也可以先做,到時候再看我們是不是做得一樣XD,一起堅持下去吧!